package cvar.solver;

import java.util.ArrayList;

import mosek.ArrayLengthException;
import mosek.Env;
import mosek.Env.boundkey;
import mosek.Error;
import mosek.Stream;
import mosek.Task;
import mosek.Warning;
import cvar.model.Constraint;
import cvar.model.LPModel;
import cvar.model.Variable;


public class MosekSolver implements Solver {

  private Env enviroment_;
  private Task task_;
  private LPModel model_;
  private String[] names_;
  
  class Msgclass extends Stream {
    public Msgclass ()
    {
      super ();
    }
    
    public void stream (String msg)
      {
        System.out.print (msg);
    }
  }
  
  public MosekSolver(LPModel model) {
    model_ = model;
    names_ = new String[model.getVariables().length];
    try {
      enviroment_ = new Env();
      Msgclass env_msg_obj = new Msgclass ();
      enviroment_.set_Stream (Env.streamtype.log, env_msg_obj);
      enviroment_.init();
      task_ = new Task(enviroment_, 0, 0);
      
      task_.putintparam(Env.iparam.intpnt_num_threads, 10);
      
      Msgclass task_msg_obj = new Msgclass ();
      task_.set_Stream (Env.streamtype.log, task_msg_obj);
      
    } catch (Exception e) {
      
    }
  }

  @Override
  public Result solve() {
    try {
      createTask();
    } catch (Exception e) {
      System.err.println(e.getMessage());
      e.printStackTrace();
    }
    try {
      solveTask();
    } catch (Exception e){
      System.err.println(e.getMessage());
      e.printStackTrace();
    }
    dispose();
    return new Result();
  }


  private void dispose() {
    if (task_ != null) task_.dispose ();
    if (enviroment_  != null)  enviroment_.dispose ();
  }

  private void solveTask() throws Warning, Error, ArrayLengthException {
    int numvar = model_.getVariables().length;
    double[] xx  = new double[numvar];
    task_.optimize();
    // Print a summary containing information
    //   about the solution for debugging purposes
    task_.solutionsummary(Env.streamtype.msg);
    
    mosek.Env.solsta solsta[] = new mosek.Env.solsta[1];
    mosek.Env.prosta prosta[] = new mosek.Env.prosta[1];
    /* Get status information about the solution */ 
    task_.getsolutionstatus(mosek.Env.soltype.bas,
                           prosta,
                           solsta);
    task_.getsolutionslice(mosek.Env.soltype.bas, // Basic solution.     
                          mosek.Env.solitem.xx,  // Which part of solution.
                          0,      // Index of first variable.
                          numvar, // Index of last variable+1 
                          xx);
    switch(solsta[0])
    {
    case optimal:
    case near_optimal:
      System.out.println("Optimal primal solution\n");
      for(int j = 0; j < numvar; ++j)
        System.out.println ("x[" + names_[j] + "]:" + xx[j]);
      break;
    case dual_infeas_cer:
    case prim_infeas_cer:
    case near_dual_infeas_cer:
    case near_prim_infeas_cer:  
      System.out.println("Primal or dual infeasibility.\n");
      break;
    case unknown:
      System.out.println("Unknown solution status.\n");
      break;
    default:
      System.out.println("Other solution status");
      break;
    }
  }

  private void createTask() throws Warning, Error, ArrayLengthException {
    int numVariable = model_.getVariables().length;
    int numConstraints = model_.getEqualities().size() + model_.getInequalities().size();
    task_.putmaxnumvar(numVariable);
    task_.putmaxnumcon(numConstraints);
    task_.putmaxnumanz(model_.numNotNullAValues());
    task_.append(Env.accmode.con, numConstraints);
    task_.append(Env.accmode.var,numVariable);
    Variable[] variables = model_.getVariables();
    double[] weights = model_.getVariableWeights();
    for (int i = 0; i < variables.length; ++i) {
      Variable x = variables[i];
      names_[i] = x.getName();
      task_.putcj(i, weights[i]);
      task_.putbound(Env.accmode.var, i, Env.boundkey.lo, 0.0, 0.0);
    }
    createA();
    task_.putobjsense(Env.objsense.minimize);
    task_.writedata("problem.lp");
  }

  private void createA() throws Warning, Error, ArrayLengthException {
    int constraintNum = 0;
    for (Constraint constraint : model_.getInequalities()) {
      addConstraint(constraintNum, constraint, Env.boundkey.up);
      ++constraintNum;
    }
    for (Constraint constraint : model_.getEqualities()) {
      addConstraint(constraintNum, constraint, Env.boundkey.fx);
      ++constraintNum;
    }
  }

  private void addConstraint(int constraintNum, Constraint constraint, boundkey boundkey) throws Warning,
      ArrayLengthException, Error {
    ArrayList<Variable> notNulls = constraint.getNotNullVariables();
    int[] notNullSpaces = new int[notNulls.size()];
    double[] notNullValues = new double[notNulls.size()];
    for (int i = 0; i < notNulls.size(); ++i) {
      Variable x = notNulls.get(i);
      notNullSpaces[i] = model_.getVariableIndex(x);
      notNullValues[i] = constraint.getVariableCoefficients().get(i);
    }
    double b = constraint.getB();
    task_.putavec(Env.accmode.con, constraintNum, notNullSpaces, notNullValues);
    task_.putbound(Env.accmode.con, constraintNum, boundkey, b, b);
  }

}
